//#include "scrambled_symbols.h"
#include "mydb.hxx"
#include "priv/mydbimpl.hxx"
#include <myfilelog.hxx>
#include <mymutex.hxx>
#include <sstream>
#include <iostream>
#include <assert.h>

using namespace std;

const tSInt32 DB::Undefined=-1;
const tSInt32 DB::SuccessButNoResult=-2;

DB::DB(const string &server_address,
	      const string &login, 
	      const string &password, 
	      const string &database,
	      tUInt16 port,FileLog *filelog)
  : _impl(0)
{
  _impl= new DBImpl(server_address,login,password,database,port,filelog);
}

DB::~DB()
{
  if (_impl)
    {
      _impl->Disconnect();
      delete _impl;
      _impl=0;
    }
}

DB::DB(const DB &model)
{
#if !defined(RELEASE)
  cerr<<"DB::DB(const DB &model): This function is not supposed to be called! Crashing."<<endl; cerr.flush();
  assert(0);
#endif
}

tBool DB::Disconnect()
{
  return _impl->Disconnect();
}

tUInt32 DB::ErrNo() const 
{
  return _impl->ErrNo();
}

const char *DB::LastErrorMsg()
{
  return _impl->LastErrorMsg();
}

tSInt32 DB::LastErrorCode()
{
  return _impl->LastErrorCode();
}

DB::tQueryId DB::Query(const string &query)
{
  return _impl->Query(query);
}

char **DB::FetchQuery(tQueryId qid)
{
  return _impl->FetchQuery(qid);
}

tUInt32 DB::NumFieldsQuery(tQueryId qid)
{
  return _impl->NumFieldsQuery(qid);
}

bool DB::FreeQuery(DB::tQueryId qid)
{
  return _impl->FreeQuery(qid);
}

bool DB::Connect()
{
  return _impl->Connect();
}

void DB::Ping()
{
  _impl->Ping();
}
tUInt32 DB::LastInsertId()
{
	return _impl->LastInsertId();
}
tUInt32 DB::AffectedRows()
{
	return _impl->AffectedRows();
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////////

// Trying to comply with the rule provided by MYSQL at:
// http://dev.mysql.com/doc/refman/5.0/en/c-api-function-overview.html
//
// Following will allow us to know when to call mysql_library_init and 
// when to call mysql_library_end
tUInt32 DBImpl::_lib_usage_counter=0;

static Mutex _lib_usage_counter_protect;

DBImpl::DBImpl(const string &server_address,
	       const string &login, 
	       const string &password, 
	       const string &database,
	       tUInt16 port,
	       FileLog *filelog)
  : _conn(0), _result_list(), 
    _server_address(server_address),
    _login(login),
    _password(password),
    _database(database),
    _port(port),
    _filelog(filelog)
{
#if defined(TRACE)
  cerr<<"DBImpl::DBImpl(server="<<server_address<<", login="<<login<<", passwd="<<password<<", db="<<database<<", port="<<port<<", filelog="<<filelog<<")"<<endl; cerr.flush();
#endif

  _lib_usage_counter_protect.Lock();
  // Trying to comply with the rule provided by MYSQL at:
  // http://dev.mysql.com/doc/refman/5.0/en/c-api-function-overview.html
  if (_lib_usage_counter==0)
    {
      mysql_library_init(0,0,0);
    }

  _lib_usage_counter++;

  _lib_usage_counter_protect.Unlock();

}

DBImpl::~DBImpl()
{
  Disconnect();

  _lib_usage_counter_protect.Lock();

  // Trying to comply with the rule provided by MYSQL at:
  // http://dev.mysql.com/doc/refman/5.0/en/c-api-function-overview.html
  _lib_usage_counter--;

  if (_lib_usage_counter==0)
    {
      mysql_library_end();
    }

  _lib_usage_counter_protect.Unlock();

}

tUInt32 DBImpl::ErrNo() const 
{
  if (_conn==0) return (tUInt32) -1;

  return mysql_errno(_conn);
}

tBool DBImpl::Connect()
{

  if (_conn!=0)
    {
      mysql_close(_conn);
      _conn=0;
    }

  _conn = mysql_init(NULL);
  if (_conn==0) return false;

  if (!mysql_real_connect(_conn, _server_address.c_str(),
			  _login.c_str(), _password.c_str(), 
			  _database.c_str(), 
			  _port, NULL, 0)) 
    {
      if (_filelog)
	{
	  stringstream ss;
	  ss<<"DBImpl::Connect: Failed!: Error code: "<<ErrNo()<<"."<<endl;
	  _filelog->AppendError(ss.str());
	}
      //Disconnect();
      return false;
    }
  
  return true;
}

tBool DBImpl::Disconnect()
{
  // Here this is a void function for MySQL.
  // So we suppose that the closing operation always succeed.
  if (_conn) 
    {
      mysql_close(_conn);
      _conn=0;
    }
  return true;
}

DB::tQueryId DBImpl::Query(const string &query)
{
   MYSQL_RES *res;
   DB::tQueryId qid;
   int result;

#if defined(TRACE)
  cerr<<"DBImpl::Query(query='"<<query<<"')"<<endl; cerr.flush();
#endif
   if (_conn==0) 
     {
#if defined(TRACE)
       cerr<<"DBImpl::Query: _conn==NULL! Reconnecting! "<<endl; cerr.flush();
#endif
       if (!Connect()) 
	 {
	   return DB::Undefined; 
	 }
     }

   
#if defined(TRACE)
  cerr<<"DBImpl::Query: Executing mysql_query."<<endl; cerr.flush();
#endif

  result=mysql_real_query(_conn, query.c_str(), query.size());

  if (result) 
    {
#if defined(DEBUG)
      if (_filelog)
	{
	  stringstream ss;
	  ss<<"DBImpl::Query: mysql_query('"<<query<<"') Failed and returned "<<result<<"!: Error code: "<<LastErrorCode()<<", Error Msg:'"<<LastErrorMsg()<<"'"<<endl;
	  _filelog->AppendDebug(ss.str());
	}
#endif
	  // in case connection lost, re-connect and try.
	  if (!Connect())
		return DB::Undefined;
	  result=mysql_real_query(_conn, query.c_str(), query.size());
	  if (result)
		  return DB::Undefined;
     }

#if defined(TRACE)
  cerr<<"DBImpl::Query: Executing mysql_store_result."<<endl; cerr.flush();
#endif
   // Store the result locally to the current MySQL client.
   res = mysql_store_result(_conn);   

   if (res == NULL)
     {
       // Checking whether the last query produced any data.
       if (mysql_field_count(_conn)==0)
	 {
	   // No.
	   return DB::SuccessButNoResult;
	 }
       if (_filelog)
	 {
	   stringstream ss;
	   ss<<"DBImpl::Query: mysql_store_result('"<<query<<"') Failed!: Error code: "<<LastErrorCode()<<", Error Msg:'"<<LastErrorMsg()<<"'"<<endl;
	   _filelog->AppendDebug(ss.str());
	 }
       return DB::Undefined;
     }

#if defined(TRACE)
  cerr<<"DBImpl::Query: Storing query result and creating query id!"<<endl; cerr.flush();
#endif
   // Store the previous result and create an id to identify this
   // query result. This is done by using an indexed list.
   qid=_result_list.push_back(res);

   return qid;
}

tSInt32 DBImpl::LastErrorCode()
{
  return mysql_errno(_conn);
}

const char *DBImpl::LastErrorMsg()
{
  return mysql_error(_conn);
}

char **DBImpl::FetchQuery(DB::tQueryId qid)
{
  return mysql_fetch_row(_result_list[qid]);
}

tUInt32 DBImpl::NumFieldsQuery(DB::tQueryId qid)
{
  return mysql_num_fields(_result_list[qid]);
}

bool DBImpl::FreeQuery(DB::tQueryId qid)
{
  mysql_free_result(_result_list[qid]);
  return(_result_list.remove(qid));
}

void DBImpl::Ping()
{
  mysql_ping(_conn);
}

tUInt32 DBImpl::LastInsertId()
{
	return (tUInt32)mysql_insert_id(_conn);
}
tUInt32 DBImpl::AffectedRows()
{
	return (tUInt32)mysql_affected_rows(_conn);
}